<template>
  {{ state.displayValue }}
</template>

<script setup lang="ts">
import { computed, onMounted, onUnmounted, reactive, watch } from 'vue'
import {
  requestAnimationFrame,
  cancelAnimationFrame
} from '@/utils/requestAnimationFrame'

defineOptions({
  // 组件名称
  name: 'CountToComp',
  // 继承透传，是否继承没在子组件声明的父组件的属性
  inheritAttrs: true
})

interface IProps {
  start?: number
  end?: number
  duration?: number
  autoPlay?: boolean
  decimals?: number
  decimal?: string
  separator?: string
  prefix?: string
  suffix?: string
  useEasing?: boolean
  easingFn?: (...args: number[]) => any
}
const props = withDefaults(defineProps<IProps>(), {
  start: 0,
  end: 0,
  duration: 5000,
  autoPlay: true,
  decimals: 0,
  decimal: '.',
  separator: ',',
  prefix: '',
  suffix: '',
  useEasing: true,
  easingFn: (t, b, c, d) =>
    (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
})

const isNum = (val: any) => !isNaN(parseFloat(val))
const formatNumber = (num: number) => {
  const val = num.toFixed(2) + ''
  const x = val.split('.')
  let x1 = x[0]
  const x2 = x.length > 1 ? props.decimal + x[1] : ''
  const rgx = /(\d+)(\d{3})/
  if (props.separator && !isNum(props.separator)) {
    while (rgx.test(x1)) {
      x1 = x1.replace(rgx, '$1' + props.separator + '$2')
    }
  }
  return props.prefix + x1 + x2 + props.suffix
}

const state = reactive<{
  localStart: number
  displayValue: string
  printVal: number | null
  paused: boolean
  localDuration: number
  startTime: number | null
  timestamp: number | null
  remainning: number | null
  rAf: any
}>({
  localStart: props.start,
  displayValue: formatNumber(props.start),
  printVal: null,
  paused: false,
  localDuration: props.duration,
  startTime: null,
  timestamp: null,
  remainning: null,
  rAf: null
})

const stopCount = computed(() => props.start > props.end)

const emit = defineEmits(['onMountedCallback', 'callback'])

const startCount = () => {
  state.localStart = props.start
  state.startTime = null
  state.localDuration = props.duration
  state.paused = false
  state.rAf = requestAnimationFrame(count)
}

watch(
  () => props.start,
  () => {
    if (props.autoPlay) {
      startCount()
    }
  }
)

watch(
  () => props.end,
  () => {
    if (props.autoPlay) {
      startCount()
    }
  }
)

onMounted(() => {
  if (props.autoPlay) {
    startCount()
  }
  emit('onMountedCallback')
})

const pause = () => {
  cancelAnimationFrame(state.rAf)
}

const resume = () => {
  state.startTime = null
  state.localDuration = state.remainning!
  state.localStart = state.printVal ?? 0
  requestAnimationFrame(count)
}
const pauseResume = () => {
  if (state.paused) {
    resume()
    state.paused = false
  } else {
    pause()
    state.paused = true
  }
}
const reset = () => {
  state.startTime = null
  cancelAnimationFrame(state.rAf)
  state.displayValue = formatNumber(props.start)
}
const count = (timestamp: number) => {
  if (!state.startTime) {
    state.startTime = timestamp
  }
  state.timestamp = timestamp
  const progress = timestamp - state.startTime
  state.remainning = state.localDuration - progress
  if (props.useEasing) {
    if (stopCount.value) {
      state.printVal =
        state.localStart -
        props.easingFn(
          progress,
          0,
          state.localStart - props.end,
          state.localDuration
        )
    } else {
      state.printVal = props.easingFn(
        progress,
        state.localStart,
        props.end - state.localStart,
        state.localDuration
      )
    }
  } else {
    if (stopCount.value) {
      state.printVal =
        state.localStart -
        ((state.localStart - props.end) * progress) / state.localDuration
    } else {
      state.printVal =
        state.localStart +
        ((props.end - state.localStart) * progress) / state.localDuration
    }
  }
  if (stopCount.value) {
    state.printVal =
      (state.printVal as number) < props.end ? props.end : state.printVal
  } else {
    state.printVal =
      (state.printVal as number) > props.end ? props.end : state.printVal
  }
  state.displayValue = formatNumber(state.printVal as number)
  if (progress < state.localDuration) {
    state.rAf = requestAnimationFrame(count)
  } else {
    emit('callback')
  }
}

onUnmounted(() => {
  cancelAnimationFrame(state.rAf)
})

defineExpose({
  reset,
  pauseResume
})
</script>

<style scoped lang="scss"></style>
